"""
合并 toweb 数据到 crystallographic_point_groups.json

功能：
1. 从 toweb/crystal_point_groups.json 读取矩阵数据
2. 通过矩阵匹配确保操作对应关系准确
3. 将 SU2、旋转轴、角度、irrep矩阵合并到现有数据
4. 保留所有原有的 Bilbao 元数据

使用方法：
    python -m app.utils.merge_toweb_data
"""

import json
from pathlib import Path
from typing import Any


def matrices_equal(m1: list[list[float]], m2: list[list[float]], tol: float = 1e-5) -> bool:
    """比较两个 3x3 矩阵是否相等（容忍浮点误差）"""
    if len(m1) != 3 or len(m2) != 3:
        return False

    for i in range(3):
        if len(m1[i]) != 3 or len(m2[i]) != 3:
            return False
        for j in range(3):
            if abs(m1[i][j] - m2[i][j]) > tol:
                return False

    return True


def normalize_complex_value(value: Any) -> dict[str, float]:
    """标准化复数值为 {real, imag} 格式"""
    if isinstance(value, dict) and "real" in value and "imag" in value:
        return {"real": float(value["real"]), "imag": float(value["imag"])}
    elif isinstance(value, (int, float)):
        return {"real": float(value), "imag": 0.0}
    else:
        return {"real": 0.0, "imag": 0.0}


def normalize_matrix(matrix: list) -> list:
    """标准化矩阵为复数格式"""
    if not matrix:
        return []

    normalized = []
    for row in matrix:
        normalized_row = []
        for val in row:
            normalized_row.append(normalize_complex_value(val))
        normalized.append(normalized_row)

    return normalized


def normalize_su2(su2_matrix: list | None) -> list[list[dict[str, float]]] | None:
    """标准化 SU2 矩阵"""
    if not su2_matrix or len(su2_matrix) != 2:
        return None

    normalized = []
    for row in su2_matrix:
        if len(row) != 2:
            return None
        normalized_row = [normalize_complex_value(val) for val in row]
        normalized.append(normalized_row)

    return normalized


# 国际符号到 Schoenflies 的映射
INTERNATIONAL_TO_SCHOENFLIES = {
    "1": "C1",
    "-1": "Ci",
    "2": "C2",
    "m": "Cs",
    "2/m": "C2h",
    "222": "D2",
    "mm2": "C2v",
    "mmm": "D2h",
    "4": "C4",
    "-4": "S4",
    "4/m": "C4h",
    "422": "D4",
    "4mm": "C4v",
    "-42m": "D2d",
    "4/mmm": "D4h",
    "3": "C3",
    "-3": "C3i",
    "32": "D3",
    "3m": "C3v",
    "-3m": "D3d",
    "6": "C6",
    "-6": "C3h",
    "6/m": "C6h",
    "622": "D6",
    "6mm": "C6v",
    "-6m2": "D3h",
    "6/mmm": "D6h",
    "23": "T",
    "m-3": "Th",
    "432": "O",
    "-43m": "Td",
    "m-3m": "Oh",
}


def merge_point_group_data(bilbao_group: dict, toweb_group: dict) -> dict:
    """
    合并单个点群的数据

    Args:
        bilbao_group: crystallographic_point_groups.json 中的点群数据
        toweb_group: toweb/crystal_point_groups.json 中的点群数据

    Returns:
        合并后的点群数据
    """
    schoenflies = bilbao_group["schoenflies"]
    print(f"  处理 {schoenflies}...")

    # 获取操作列表
    bilbao_ops = bilbao_group["operations"]
    toweb_ops = toweb_group["operations"]

    # 通过矩阵匹配建立操作对应关系
    operation_mapping = {}  # bilbao_index -> toweb_index

    for bilbao_idx, bilbao_op in enumerate(bilbao_ops):
        bilbao_matrix = bilbao_op["matrix"]

        for toweb_idx, toweb_op in enumerate(toweb_ops):
            toweb_matrix = toweb_op["rot"]

            if matrices_equal(bilbao_matrix, toweb_matrix):
                operation_mapping[bilbao_idx] = toweb_idx
                break

    # 检查是否所有操作都匹配
    if len(operation_mapping) != len(bilbao_ops):
        print(f"    ⚠️  警告：只匹配了 {len(operation_mapping)}/{len(bilbao_ops)} 个操作")
    else:
        print(f"    ✓ 匹配了 {len(operation_mapping)} 个操作")

    # 合并操作数据
    for bilbao_idx, bilbao_op in enumerate(bilbao_ops):
        if bilbao_idx not in operation_mapping:
            print(f"    ⚠️  操作 {bilbao_op['id']} ({bilbao_op.get('seitz', 'N/A')}) 未找到匹配")
            continue

        toweb_idx = operation_mapping[bilbao_idx]
        toweb_op = toweb_ops[toweb_idx]

        # 添加 SU2 矩阵
        bilbao_op["su2"] = normalize_su2(toweb_op.get("su2"))

        # 添加旋转轴和角度
        bilbao_op["axis"] = [
            toweb_op.get("nx", 0.0),
            toweb_op.get("ny", 0.0),
            toweb_op.get("nz", 0.0),
        ]
        bilbao_op["angle"] = toweb_op.get("angle", 0.0)

        # 计算行列式
        m = bilbao_op["matrix"]
        det = (
            m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1])
            - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
            + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
        )
        bilbao_op["det"] = int(round(det))

    # 合并 irrep 矩阵
    toweb_reps = toweb_group.get("representations", [])

    if toweb_reps:
        # 建立 irrep 标签映射（GM1 -> Gamma 标签）
        # 从 character_table 获取 Gamma 标签
        bilbao_irreps = bilbao_group.get("character_table", {}).get("irreps", [])

        # 过滤掉 Mult. 行，只保留真正的不可约表示
        real_bilbao_irreps = [irrep for irrep in bilbao_irreps if irrep.get("mulliken") != "Mult."]

        # 获取 Bilbao 的操作-类映射
        # character_table.classes 是类的列表，character_table.operations 是操作的列表
        # 需要将类特征标展开到操作级别
        bilbao_char_operations = bilbao_group.get("character_table", {}).get("operations", [])
        bilbao_char_classes = bilbao_group.get("character_table", {}).get("classes", [])

        # 为每个 bilbao irrep 构建操作级别的特征标
        bilbao_irreps_with_op_chars = []
        for irrep in real_bilbao_irreps:
            class_chars = irrep.get("characters", [])  # 类级别的特征标

            # 将类特征标展开到操作级别
            # 对于每个操作，找到它对应的类，然后取该类的特征标
            op_chars = []
            for op_label in bilbao_char_operations:
                # 简化！对于 D6，操作和类的顺序是对应的（每个类只有一个操作）
                # 但更通用的做法是建立映射关系
                # 这里我们假设 operations 和 classes 长度相同时是一一对应的
                if len(bilbao_char_operations) == len(bilbao_char_classes):
                    op_idx = bilbao_char_operations.index(op_label)
                    if op_idx < len(class_chars):
                        op_chars.append(class_chars[op_idx])

            bilbao_irreps_with_op_chars.append({"irrep": irrep, "op_chars": op_chars})

        # 使用特征标匹配建立 toweb -> bilbao的映射
        toweb_to_bilbao_map = {}  # toweb_idx -> bilbao_irrep
        used_bilbao_irreps = set()  # 记录已使用的 bilbao irrep

        for toweb_idx, toweb_rep in enumerate(toweb_reps):
            toweb_chars = toweb_rep.get("characters", [])
            toweb_label = toweb_rep.get("label", "")

            # 尝试通过特征标匹配找到对应的 bilbao 表示
            best_match = None
            min_diff = float("inf")
            max_matched = 0  # 记录成功匹配的特征标数量

            for bilbao_data in bilbao_irreps_with_op_chars:
                bilbao_irrep = bilbao_data["irrep"]
                bilbao_op_chars_str = bilbao_data["op_chars"]

                # 跳过已使用的 irrep
                if id(bilbao_irrep) in used_bilbao_irreps:
                    continue

                # 计算特征标差异
                diff = 0
                matched_count = 0  # 成功比较的特征标数量

                # 比较 toweb 操作特征标与 bilbao 操作特征标
                # toweb 有 12 个操作，bilbao character_table.operations 可能有 6 个或12个
                min_len = min(len(toweb_chars), len(bilbao_op_chars_str))

                for i in range(min_len):
                    tc = toweb_chars[i]
                    bc_str = bilbao_op_chars_str[i]

                    try:
                        # 尝试解析 bilbao 特征标（可能是 "1", "-1", "2", "w", 等）
                        if bc_str in ["w", "w2", "-w", "-w2"]:
                            # 复数特征标，跳过精确比较
                            continue
                        bc = float(bc_str)

                        # 处理 toweb 特征标（可能是数值或复数字典）
                        if isinstance(tc, dict):
                            # 复数特征标，跳过比较
                            continue

                        diff += abs(tc - bc)
                        matched_count += 1
                    except (ValueError, TypeError):
                        # 无法解析为数值，跳过
                        continue

                # 选择标准：优先选择匹配数量多的，其次选择差异小的
                if matched_count > max_matched or (matched_count == max_matched and diff < min_diff):
                    min_diff = diff
                    max_matched = matched_count
                    best_match = bilbao_irrep

            if best_match and max_matched > 0:
                # 有成功的特征标匹配
                toweb_to_bilbao_map[toweb_idx] = best_match
                used_bilbao_irreps.add(id(best_match))
            elif toweb_idx < len(real_bilbao_irreps):
                # 如果匹配失败，使用索引作为后备方案
                # 但要找到第一个未使用的 irrep
                found_unassigned = False
                for irrep in real_bilbao_irreps:
                    if id(irrep) not in used_bilbao_irreps:
                        toweb_to_bilbao_map[toweb_idx] = irrep
                        used_bilbao_irreps.add(id(irrep))
                        found_unassigned = True
                        break
                if not found_unassigned:
                    # Fallback if no unassigned irrep is found (shouldn't happen if lengths match)
                    toweb_to_bilbao_map[toweb_idx] = real_bilbao_irreps[toweb_idx]

        # 构建 irrep 矩阵字典
        irrep_matrices_by_op = {}  # op_id -> {label: matrix}

        for toweb_idx, toweb_rep in enumerate(toweb_reps):
            toweb_label = toweb_rep["label"]  # GM1, GM2, ...
            matrices = toweb_rep.get("matrices", [])

            # 获取对应的标签（优先使用 Mulliken，其次 Gamma，最后 fallback）
            final_label = toweb_label
            if toweb_idx in toweb_to_bilbao_map:
                bilbao_irrep = toweb_to_bilbao_map[toweb_idx]
                # 优先使用 Mulliken 符号
                if bilbao_irrep.get("mulliken"):
                    final_label = bilbao_irrep.get("mulliken")
                # 回退到 Gamma 符号
                elif bilbao_irrep.get("gamma"):
                    final_label = bilbao_irrep.get("gamma")

            # 为每个操作添加该 irrep 的矩阵
            for bilbao_idx, bilbao_op in enumerate(bilbao_ops):
                if bilbao_idx not in operation_mapping:
                    continue

                toweb_idx_op = operation_mapping[bilbao_idx]

                if toweb_idx_op < len(matrices):
                    op_id = bilbao_op["id"]

                    if op_id not in irrep_matrices_by_op:
                        irrep_matrices_by_op[op_id] = {}

                    # 标准化矩阵格式
                    irrep_matrices_by_op[op_id][final_label] = normalize_matrix(matrices[toweb_idx_op])

        # 将 irrep_matrices 添加到每个操作
        for bilbao_op in bilbao_ops:
            op_id = bilbao_op["id"]
            bilbao_op["irrep_matrices"] = irrep_matrices_by_op.get(op_id)

    print(f"    ✓ 添加了 {len(toweb_reps)} 个不可约表示的矩阵")

    return bilbao_group


def merge_data():
    """主函数：合并数据"""
    # 文件路径
    project_root = Path(__file__).parent.parent.parent.parent
    toweb_file = project_root / "toweb" / "crystal_point_groups.json"
    bilbao_file = project_root / "server" / "app" / "data" / "crystallographic_point_groups.json"
    output_file = bilbao_file  # 直接覆盖原文件
    backup_file = bilbao_file.with_suffix(".json.backup")

    # 检查文件存在
    if not toweb_file.exists():
        raise FileNotFoundError(f"找不到 toweb 数据文件: {toweb_file}")

    if not bilbao_file.exists():
        raise FileNotFoundError(f"找不到 Bilbao 数据文件: {bilbao_file}")

    # 备份原文件
    print(f"📦 备份原文件到: {backup_file}")
    import shutil

    shutil.copy(bilbao_file, backup_file)

    # 读取数据
    print(f"📖 读取 toweb 数据: {toweb_file}")
    with open(toweb_file, "r", encoding="utf-8") as f:
        toweb_data = json.load(f)

    print(f"📖 读取 Bilbao 数据: {bilbao_file}")
    with open(bilbao_file, "r", encoding="utf-8") as f:
        bilbao_data = json.load(f)

    # 建立 toweb 点群映射（按国际符号）
    toweb_by_international = {}
    for group in toweb_data:
        pg_label = group["pg_label"]
        toweb_by_international[pg_label] = group

    print("\n🔄 开始合并数据...")
    print(f"   Bilbao 点群数量: {len(bilbao_data)}")
    print(f"   Toweb 点群数量: {len(toweb_data)}")

    # 合并每个点群
    merged_count = 0
    for bilbao_group in bilbao_data:
        international = bilbao_group.get("international")

        if international in toweb_by_international:
            toweb_group = toweb_by_international[international]
            merge_point_group_data(bilbao_group, toweb_group)
            merged_count += 1
        else:
            print(f"  ⚠️  未找到对应的 toweb 数据: {bilbao_group['schoenflies']} ({international})")

    print(f"\n✅ 成功合并 {merged_count}/{len(bilbao_data)} 个点群")

    # 写入合并后的数据
    print(f"\n💾 写入合并后的数据: {output_file}")
    with open(output_file, "w", encoding="utf-8") as f:
        json.dump(bilbao_data, f, indent=2, ensure_ascii=False)

    print("\n✨ 合并完成！")
    print(f"   原文件已备份到: {backup_file}")
    print(f"   合并后的数据已写入: {output_file}")

    # 显示统计信息
    print("\n📊 统计信息:")
    total_ops = sum(len(g["operations"]) for g in bilbao_data)
    ops_with_su2 = sum(1 for g in bilbao_data for op in g["operations"] if op.get("su2"))
    ops_with_irreps = sum(1 for g in bilbao_data for op in g["operations"] if op.get("irrep_matrices"))

    print(f"   总操作数: {total_ops}")
    print(f"   包含 SU2 矩阵: {ops_with_su2}")
    print(f"   包含 irrep 矩阵: {ops_with_irreps}")


if __name__ == "__main__":
    merge_data()
